#include "JSON_settings.h"

#include <QCoreApplication>
#include <QVariant>
#include <QFile>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>

#include <QDebug>

static QString searchConfigFile();

static bool ReadJsonSettingsFile(QIODevice &device, QSettings::SettingsMap &map);
static bool WriteJsonSettingsFile(QIODevice &device, const QSettings::SettingsMap &map);
static void FlattenVariantMap(const QVariantMap &input, const QString &prefix, QSettings::SettingsMap *settings);
static void SetValueToJson(QJsonObject &obj, const QStringList &names, int index, const QVariant &value);
static bool WriteValueToJson(QJsonObject &doc, const QString &key, const QVariant &value);

namespace JSONSettings {

QSettings::Format cJsonFormat;
QString settingFilePath;

bool Initialize()
{
    cJsonFormat = QSettings::registerFormat("json", ReadJsonSettingsFile, WriteJsonSettingsFile, Qt::CaseSensitive);
	if (cJsonFormat != QSettings::InvalidFormat)
		QSettings::setDefaultFormat(cJsonFormat);
    settingFilePath = searchConfigFile();

	return (!settingFilePath.isEmpty() && cJsonFormat != QSettings::InvalidFormat);
}

QSettings::Format GetFormat()
{
	return cJsonFormat;
}

QString GetSettingFilePath()
{
	return settingFilePath;
}

}  // namespace QtJsonSettings

int GetVariantMetaType(const QVariant& var)
{
#if QT_VERSION_MAJOR == 6
	return var.typeId();
#else
	return static_cast<int>(var.type());
#endif
}

static void FlattenVariantMap(const QVariantMap &input, const QString &prefix, QSettings::SettingsMap *settings)
{
	for (auto iter = input.cbegin(); iter != input.cend(); ++iter) {
		if (GetVariantMetaType(*iter) == QMetaType::QVariantMap)
			FlattenVariantMap(iter->toMap(), prefix + iter.key() + "/", settings);
		else
			(*settings)[prefix + iter.key()] = (*iter);
	}
}

static bool ReadJsonSettingsFile(QIODevice &device, QSettings::SettingsMap &settings)
{
	QByteArray data = device.readAll();
	QJsonDocument doc = QJsonDocument::fromJson(data);
	if (doc.object().isEmpty())		// is array
		return false;

	QVariantMap values = doc.object().toVariantMap();
	FlattenVariantMap(values, "", &settings);
	return true;
}

static void SetValueToJson(QJsonObject &obj, const QStringList &names, int index, const QVariant &value)
{
	if (index == names.size() - 1) {
		obj[names.last()] = QJsonValue::fromVariant(value);
		return;
	}

	if (!obj.contains(names[index]))
		obj.insert(names[index], QJsonObject());

	QJsonObject new_obj = obj[names[index]].toObject();
	SetValueToJson(new_obj, names, index + 1, value);
	obj[names[index]] = new_obj;
}

static bool WriteValueToJson(QJsonObject &doc, const QString &key, const QVariant &value)
{
    QStringList names = key.split("/", Qt::SkipEmptyParts);

	if (names.size() == 0)
	  return true;

	SetValueToJson(doc, names, 0, value);
	return true;
}

static bool WriteJsonSettingsFile(QIODevice &device, const QSettings::SettingsMap &settings)
{
	QJsonDocument doc;
	doc.setObject(QJsonObject());

	QJsonObject root = doc.object();

	for (QString &key : settings.keys()) {
		if (!WriteValueToJson(root, key, settings[key]))
			return false;
	}

	doc.setObject(root);
	device.write(doc.toJson());

	return true;
}

static QString searchConfigFile()
{
    QString binDir = QCoreApplication::applicationDirPath();
    QString fileName = QString(SETTING_APPLICATION_NAME) + ".json";

    QStringList searchList;
    searchList.append(binDir);
    searchList.append(binDir + "/../");     // for development with shadow build (Linux)
    searchList.append(binDir + "/../../");  // for development with shadow build (Windows)

    foreach (QString dir, searchList) {
        QFile file(dir + "/" + fileName);
        if (file.exists()) {
            fileName = QDir(file.fileName()).canonicalPath();
            qInfo("Using config file %s", qPrintable(fileName));
            return fileName;
        }
    }

    // not found
    foreach (QString dir, searchList) {
        qWarning("%s/%s not found", qPrintable(dir), qPrintable(fileName));
    }
    qFatal("Cannot find config file %s",qPrintable(fileName));
    return QString("");
}
